<?php
declare(strict_types=1);

namespace Framework\Support;

use FastRoute;
use Framework\Libs\CustomException;


class Request
{
    public array $request;
    private \Swoole\Http\Request|null $req;
    public int $time = 0;
    public float $start_time = 0;
    public int $start_memory = 0;

    public array $header = [];
    public array $server = [];
    public string $action = 'Index';
    public string $controller = '\Index\Index';
    public string $ctrl = '';

    public array $route = [];
    public bool $is_ajax;
    public int $fd;

    /**
     * @throws CustomException
     */
    public function __construct(\Swoole\Http\Request $req)
    {

        $this->header = array_change_key_case($req->header,CASE_LOWER);
        $this->server = array_change_key_case(array_merge((array)$req->server, (array)$req->header), CASE_UPPER);
        $specialHeaders = [
            'X-CLIENT-SCHEME' => 'SCHEME',
            'ALI-CDN-REAL-IP' => 'X-REAL-IP',
            'ACCESS-CONTROL-REQUEST-HEADERS' => 'X-REQUESTED-WITH'
        ];
        foreach ($specialHeaders as $sourceHeader => $targetHeader) {
            if (!empty($this->server[$sourceHeader])) {
                $this->server[$targetHeader] = $this->server[$sourceHeader];
            }
        }
        //  ["access-control-request-method"]=>
        //  string(4) "POST"
        //  ["access-control-request-headers"]=>
        //  string(16) "x-requested-with"
        $this->is_ajax = !empty($this->server['X-REQUESTED-WITH']) && (strtolower($this->server['X-REQUESTED-WITH']) == 'xmlhttprequest'|| strtolower($this->server['X-REQUESTED-WITH'])=='x-requested-with');
        //$this->is_ajax = !empty($this->server['X-REQUESTED-WITH']) && strtolower($this->server['X-REQUESTED-WITH']) == 'xmlhttprequest';

        $request_url = ltrim(str_replace(['//', '/?'], '/', strtolower($this->server["REQUEST_URI"])), '/');
        $routeInfo = $_ENV['dispatcher']->dispatch($this->server['REQUEST_METHOD'], $request_url);

        if ($routeInfo[0] == FastRoute\Dispatcher::FOUND) {
            $route = explode('/', $routeInfo[1]); // 获得处理函数
            $arr = array_merge($route, $routeInfo[2]);
        } else {
            $request = explode('?', $request_url);
            $route = explode('/', $request[0]);
            $arr = [];
            if (!empty($request[1])) {
                parse_str($request[1], $arr);
            }
        }

        empty($route[0]) && $route[0] = "Index";
        empty($route[1]) && $route[1] = "Index";
        empty($route[2]) && $route[2] = "Index";
        !empty($route[5]) && $route = array_slice($route, 0, 4);
        $route = array_map(function ($word) {
            return ucfirst($word);
        }, $route);

        $action = array_pop($route);
        $controller = '\\' . implode("\\", $route);
        isset($controller[64]) && $controller = '\Index\Index';

        $controller = strtoupper($controller);
        if (!isset($_ENV['_controller_name'][$controller])) {
            $route[] = "Index";
            if (!isset($_ENV['_controller_name'][$controller . '\INDEX'])) {
                throw new CustomException('404 Page Error', 404, null, ['is_ajax' => $this->is_ajax]);
            }
            $controller = $controller . '\INDEX';
        }
        $action_method = $action . '_' . $this->server['REQUEST_METHOD'];


        if (isset($_ENV['_controller_name'][$controller][$action_method])) {
            $action = $action_method;
        } else if (!isset($_ENV['_controller_name'][$controller][$action])) {
            throw new CustomException('404 Page Error', 404, null, ['is_ajax' => $this->is_ajax]);
        }

        $this->ctrl = $controller;
        $this->controller = $_ENV['_controller_name'][$controller]['C'];
        $this->action = $action;
        $this->route = $route;
        $this->fd = $req->fd;
        $this->time = time();
        $this->req = $req;
        $prefixes = _CONF('cookie_prefixes');
        $sid_key = _CONF('session_id');
        empty($sid_key) and $sid_key = 'session_id';
        if (!empty($this->server['AUTHORIZATION'])) {
            $tk = explode(' ', $this->server['AUTHORIZATION']);
            $this->req->cookie[$prefixes . $sid_key] = !empty($tk[1]) ? $tk[1] : $tk[0];
            $this->server['X-REQUESTED-WITH'] = 'xmlhttprequest';
            $this->is_ajax = true;
        }
        // Set default scheme if not provided
        $this->server['HTTP_HOST'] = '//' . $this->server['HOST'];
        if($this->server['REQUEST_METHOD']=="POST"){
            $post = $this->getContent();
            $req->post = $post ? xn_json_decode($post):[];
        }
        $this->request = array_merge((array)$req->get, (array)$req->post ,  $arr);
        return $this;
    }

    public function getMethod(): string
    {
        return $this->_S('REQUEST_METHOD');
    }

    /**
     * Get original post body
     *
     * @access public
     * @return string
     */

    public function getContent(): string|bool
    {
        return $this->req->rawContent();
    }

    public function getUri(): string
    {
        if (null !== $qs = $this->_S('QUERY_STRING')) {
            $qs = '?' . $qs;
        }
        return $this->_s('HTTP_HOST') .$this->_S('REQUEST_URI'). $qs;
    }

    public function getContentType(): string
    {
        return $this->_S('CONTENT-TYPE');
    }

    public function getFiles(): array
    {
        if (!empty($this->req->files)) {
            return $this->req->files;
        }
        return [];
    }

    public function param(string|int $key = '', $def_val = '', $safe = true)
    {

        if (empty($key) && $key !== 0) {
            if (empty($this->request)) {
                return $def_val;
            }
            $val = $this->param_force($this->request, $def_val, $safe);
        } else {
            //$key !== 0 && $key = strtolower($key);
            switch ($key) {
                case 'page':
                    !isset($this->request['page']) && $this->request['page'] = 1;
                    $val= (int)max(1, min(100000000, $this->request['page']));
                    break;
                case 'limit':
                    !isset($this->request['limit']) && $this->request['limit'] = 1000;
                    $val= (int)max(1, min(10000, $this->request['limit']));
                    break;
                default:
                    if (!isset($this->request[$key]) || ($key === 0 && empty($this->request[$key]))) {
                        return $def_val;
                    }
                    $val = $this->param_force($this->request[$key], $def_val, $safe);
            }
        }
        return $val;
    }

    public function post(string|int $key = '', $def_val = '', $safe = true)
    {
        if (empty($key) && $key !== 0) {
            if (empty($this->req->post)) {
                return $def_val;
            }
            $val = $this->param_force($this->req->post, $def_val, $safe);
        } else {
            //$key !== 0 && $key = strtolower($key);
            switch ($key) {
                case 'page':
                    !isset($this->req->post['page']) && $this->req->post['page'] = 1;
                    $val= (int)max(1, min(100000000, $this->req->post['page']));
                    break;
                case 'limit':
                    !isset($this->req->post['limit']) && $this->req->post['limit'] = 10;
                    $val= (int)max(1, min(10000, $this->req->post['limit']));
                    break;
                default:
                    if (!isset($this->req->post[$key]) || ($key === 0 && empty($this->req->post[$key]))) {
                        return $def_val;
                    }
                    $val = $this->param_force($this->req->post[$key], $def_val, $safe);
            }
        }
        return $val;
    }

    public function get($key = '', $def_val = '', $safe = true)
    {

        if (empty($key) && $key !== 0) {
            if (empty($this->req->get)) {
                return $def_val;
            }
            $val = $this->param_force($this->req->get, $def_val, $safe);
        } else {
            //$key !== 0 && $key = strtolower($key);
            switch ($key) {
                case 'page':
                    !isset($this->req->get['page']) && $this->req->get['page'] = 1;
                    $val= (int)max(1, min(100000000, $this->req->get['page']));
                    break;
                case 'limit':
                    !isset($this->req->get['limit']) && $this->req->get['limit'] = 10;
                    $val= (int)max(1, min(10000, $this->req->get['limit']));
                    break;
                default:
                    if (!isset($this->req->get[$key]) || ($key === 0 && empty($this->req->get[$key]))) {
                        return $def_val;
                    }
                    $val = $this->param_force($this->req->get[$key], $def_val, $safe);
            }
        }
        return $val;
    }


    public function cookie($key = '', $def_val = '', $safe = true)
    {
        if (empty($key) && $key !== 0) {
            if (empty($this->req->cookie)) {
                return $def_val;
            }
            $val = $this->param_force($this->req->cookie, $def_val, $safe);
        } else {
            //$key !== 0 && $key = strtoupper($key);
            switch ($key) {
                case 'page':
                    !isset($this->req->cookie['page']) && $this->req->cookie['page'] = 1;
                    $val= (int)max(1, min(100000000, $this->req->cookie['page']));
                    break;
                case 'limit':
                    !isset($this->req->cookie['limit']) && $this->req->cookie['limit'] = 10;
                    $val= (int)max(1, min(10000, $this->req->cookie['limit']));
                    break;
                default:
                    if (!isset($this->req->cookie[$key]) || ($key === 0 && empty($this->req->cookie[$key]))) {
                        return $def_val;
                    }
                    $val = $this->param_force($this->req->cookie[$key], $def_val, $safe);
            }
        }
        return $val;
    }

    public function _S(string|int $key = '', $def_val = '', $safe = true)
    {
        if (empty($key) && $key !== 0) {
            $val = $this->param_force($this->server, $def_val, $safe);
        } else {
            $key !== 0 && $key = strtoupper($key);
            if (!isset($this->server[$key]) || ($key === 0 && empty($this->server[$key]))) {
                return $def_val;
            }
            $val = $this->param_force($this->server[$key], $def_val, $safe);
        }
        return $val;
    }

    public function param_force($val, $def_val, $safe = true)
    {
        if (is_array($def_val)) {
            $def_val = !isset($def_val) ? '' : ($def_val[0] ?? ''); // 数组的第一个元素，如果没有则为空字符串
            is_string($val) && $val = explode($def_val[1] ?? ',', $val);
            if (is_array($val)) {
                foreach ($val as $k => $v) {
                    if (is_array($v)) {
                        foreach ($v as $kk => $vv) {
                            $val[$k][$kk] = $this->param_force($vv, $def_val, $safe);
                        }
                    } else {
                        if (is_string($def_val)) {
                            $val[$k] = trim($v);
                            if ($val[$k] == 'true') {
                                $val[$k] = 1;
                            } elseif ($val[$k] == 'false') {
                                $val[$k] = 0;
                            }
                            $safe and $val[$k] = htmlspecialchars($v);
                        } else {
                            $val[$k] = intval($v);
                        }
                    }
                }
            } else {
                return [];
            }
        } else {
            if (is_array($val)) {
                foreach ($val as $kk => $vv) {
                    $val[$kk] = $this->param_force($vv, $def_val, $safe);
                }
            } else {
                if (is_string($val)) {
                    $val = trim($val);
                    if ($val == 'true') {
                        $val = 1;
                    } elseif ($val == 'false') {
                        $val = 0;
                    }
                } else {
                    $val = intval($val);
                }
            }
        }
        return $val;
    }


    /**
     * Finish request, release resources
     *
     * @access public
     */
    public function __destruct()
    {
        $this->req = null;
        $this->request = null;
        $this->server = [];
        $this->route = [];
    }

    /**
     * 检测是否使用手机访问
     *
     * @access public
     * @return bool
     */
    public function isMobile()
    {
        if ($this->_S('VIA') && stristr($this->_S('VIA'), "wap")) {
            return true;
        } else if ($this->_S('ACCEPT') && strpos(strtoupper($this->_S('ACCEPT')), "VND.WAP.WML")) {
            return true;
        } else if ($this->_S('X-WAP-PROFILE') || $this->_S('PROFILE')) {
            return true;
        } else if ($this->_S('USER-AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->_S('USER-AGENT'))) {
            return true;
        }
        return false;
    }

    public function isWechat()
    {
        $user_agent = $this->_S('USER-AGENT');
        if (strpos($user_agent, 'MicroMessenger') === false) {
            return false;
        } else {
            preg_match('/.*?(MicroMessenger\/([0-9.]+))\s*/', $user_agent, $matches);
            return $matches[2];
        }
    }

    public function get_client_ip($type = 0)
    {
        $type = $type ? 1 : 0;
        $ip = $this->_S('REMOTE-ADDR');
        empty($ip) && $ip = $this->_S('REMOTE_ADDR');
        $newIp = $this->_S('X-FORWARDED-FOR');
        $renewIp = $this->_S('X-REAL-IP');
        if (!empty($renewIp)) {
            $renewIp = explode(",", $renewIp);
            $ip = $renewIp[0];
        } else if (!empty($newIp)) {
            $newIp = explode(",", $newIp);
            $ip = $newIp[0];
        }
        $long = sprintf("%u", ip2long($ip));
        $ip = $long ? [$ip, $long] : ['0.0.0.0', 0];
        return $ip[$type];
    }
}
